use super::{
    err, err_value, json, ok, throw_err, Base, CSend, Client, Collect, ECode, KeyNode, Level, Lock,
    MapSv, RNull, Resp, SvCache, VBase, VMap, Value, VecS, VecSv,
};
use std::collections::HashSet;

pub struct Task;

impl Base for Task {}

impl Task {
    pub async fn do_cmd(
        &self,
        para: &Value,
        cmd: &str,
        client: &Client,
        cache: SvCache,
        lock: Lock,
        tx: CSend,
    ) -> Resp {
        let data = para.ckobj()?;
        let line = data.ck_str("line")?;

        let collect = Collect::new(client, cache, line);

        match cmd {
            "list" => {
                let s = data.ck_str("s")?;
                let e = data.ck_str("e")?;
                let is_count = match data.k_bool("count") {
                    Some(v) => v,
                    None => false,
                };

                let help = Level::new("task", &collect);
                let body = help.list_day(s, e, is_count).await?;

                ok!(body)
            }
            "movepsn" => {
                let _ = lock.lock().await;

                let sn = data.ck_str("k")?;
                let val = data.ck_array("v")?;

                let k_task = collect.key_sn("task", &sn)?;
                if let Err(_) = collect.item(&k_task).await {
                    throw_err!(ECode::Para, "no task");
                }

                let k_sn = collect.key_sub_sn("task", "psn", sn)?;

                let mut put = Vec::new();
                collect.merge_mass_vec(&k_sn, val, &mut put)?;

                collect.commit(Some(&put), None).await?;

                ok!()
            }
            "rm" => self.do_rm(data, lock, &collect).await,
            "rmpsn" => self.do_rmpsn(data, lock, &collect).await,
            "rmonth" => self.rm_m(data, lock, &collect).await,
            "md" => self.do_md(data, lock, &collect, &tx).await,
            "mmd" => self.do_mmd(data, lock, &collect, &tx).await,
            "add" => self.do_add(data, lock, &collect, &tx).await,
            "get" => collect.do_cmd("task", cmd, para, lock, None).await,
            "run" => self.do_run(data, lock, &collect).await,
            "pause" => {
                let k_pause = collect.key_pause("task")?;

                let is_count = match data.k_bool("count") {
                    Some(v) => v,
                    None => false,
                };

                let count = if is_count {
                    Some(vec!["psn".to_string()])
                } else {
                    None
                };

                let body = collect.list_array(&k_pause, "task", count).await?;
                ok!(body)
            }
            "setcur" => {
                let _ = lock.lock().await;

                let task_sn = data.ck_str("k")?;
                let k_cur_task = collect.key_sn("cur", "task")?;

                let task = collect.item_sn("task", &task_sn).await?;
                let state = task.ck_str("state")?;

                if state != "open" {
                    throw_err!(ECode::Task, "task must be open");
                }

                let cmd = json!({"curtask": task_sn});
                collect.update_file("cur", &k_cur_task, &cmd).await?;

                ok!()
            }
            "getcur" => {
                let _ = lock.lock().await;

                let k_cur_task = collect.key_sn("cur", "task")?;

                let body = collect.get_file("cur", &k_cur_task)?;
                let task_sn = body.ck_str("curtask")?;

                if task_sn.is_empty() {
                    throw_err!(ECode::Task, "no cur task");
                }

                let task = collect.item_sn("task", task_sn).await?;
                let state = task.ck_str("state")?;

                if state != "open" {
                    throw_err!(ECode::Task, "no open task");
                }

                ok!(task)
            }
            _ => throw_err!(ECode::NoServe, "no task serve"),
        }
    }

    async fn md_item(
        &self,
        task_sn: &str,
        val: &MapSv,
        collect: &Collect<'_>,
        tx: &CSend,
        put: &mut VecSv,
        del: &mut VecS,
    ) -> RNull {
        let k_task = collect.key_sn("task", &task_sn)?;

        if let Some(new_sn) = val.k_str("sn") {
            if task_sn != new_sn {
                throw_err!(ECode::Para, "sn not the same");
            }
        }

        let body = collect.item(&k_task).await?;
        let body = body.ckobj()?;
        let s = body.ck_str("pstart")?;

        let mut ret = body.clone();

        for (k, v) in val.iter() {
            ret.insert(k.to_string(), v.clone());
        }

        let (tech_sn, task_sn, pstart, state) = self.ck_val(&ret)?;

        let k_tech = collect.key_sn("tech", &tech_sn)?;

        // check tech
        collect.item(&k_tech).await?;

        put.push((k_task.to_string(), json!(ret)));

        let help = Level::new("task", collect);
        help.md_day_item(s, &pstart, &task_sn, put, del)?;

        self.md_run(&task_sn, &pstart, &state, collect, tx).await?;

        let k_pause = collect.key_pause("task")?;

        if state == "pause" {
            collect.merge_mass(&k_pause, &task_sn, put);
        } else {
            collect.del_mass_item(&k_pause, &task_sn, put, del);
        }

        Ok(())
    }

    async fn do_mmd(&self, data: &MapSv, lock: Lock, collect: &Collect<'_>, tx: &CSend) -> Resp {
        let _ = lock.lock().await;
        let v = data.ck_array("v")?;

        let mut put = Vec::new();
        let mut del = Vec::new();

        for d in v.iter() {
            let val = d.ckobj()?;
            let task_sn = val.ck_str("sn")?;

            let _ = self
                .md_item(task_sn, val, collect, tx, &mut put, &mut del)
                .await?;
        }

        collect.commit(Some(&put), Some(&del)).await?;

        ok!()
    }

    async fn do_md(&self, data: &MapSv, lock: Lock, collect: &Collect<'_>, tx: &CSend) -> Resp {
        let _ = lock.lock().await;

        let task_sn = data.ck_str("k")?;
        let val = data.ck_obj("v")?;

        let mut put = Vec::new();
        let mut del = Vec::new();

        let _ = self
            .md_item(task_sn, val, collect, tx, &mut put, &mut del)
            .await?;

        collect.commit(Some(&put), Some(&del)).await?;

        ok!()
    }

    // 1. remove month
    // 2. update or remove all
    // 3. remove all task of month
    // 4. remove psn of task of month
    // 5. remove task from run
    // 6. remove task from task
    // 7. remove psn from station
    async fn rm_m(&self, data: &MapSv, lock: Lock, collect: &Collect<'_>) -> Resp {
        let _ = lock.lock().await;

        let tm = data.ck_str("k")?;
        let k_run = collect.key_run("task")?;
        let k_pause = collect.key_pause("task")?;

        let mut put = Vec::new();
        let mut del = Vec::new();
        let mut psn_set = HashSet::new();

        let help = Level::new("task", collect);
        let task_set = help.rm_month(&tm, &mut put, &mut del).await?;

        for d in &task_set {
            let k_task_psn = collect.key_sub_sn("task", "psn", d)?;

            if let Ok(tp) = collect.content(&k_task_psn).await {
                for t in tp.iter() {
                    let k_psn = collect.key_sn("psn", t)?;

                    del.push(k_psn);
                    psn_set.insert(t.to_string());
                }
            }

            del.push(k_task_psn);
        }

        collect.del_mass_set(&k_run, &task_set, true, &mut put, &mut del);
        collect.del_mass_set(&k_pause, &task_set, false, &mut put, &mut del);

        let _ = collect.del_station_psn(&psn_set, &mut put, &mut del).await;

        collect.commit(Some(&put), Some(&del)).await?;

        ok!()
    }

    /* 1. rm self
     * 2. rm task-month content
     * 3. rm task content if task-month is empty
     * 4. rm run content
     * 5. rm pause content
     * 6. rm task-psn
     * 7. rm all psn in task-psn
     * 8. rm station-psn content's psn
     */
    async fn do_rm(&self, data: &MapSv, lock: Lock, collect: &Collect<'_>) -> Resp {
        let _ = lock.lock().await;

        let task_sn = data.ck_str("k")?;
        let k_psn = collect.key_sub_sn("task", "psn", task_sn)?;
        let k_run = collect.key_run("task")?;
        let k_pause = collect.key_pause("task")?;

        let mut put = Vec::new();
        let mut del = Vec::new();

        let help = Level::new("task", collect);
        help.rm_day_item(&task_sn, &mut put, &mut del).await?;

        collect.del_run_item(&k_run, task_sn, &mut put, &mut del);

        collect.del_mass_item(&k_pause, task_sn, &mut put, &mut del);

        let psn_set = collect.del_mass(Some("psn"), &k_psn, &mut del).await?;

        let _ = collect.del_station_psn(&psn_set, &mut put, &mut del).await;

        collect.commit(Some(&put), Some(&del)).await?;

        ok!()
    }

    async fn do_rmpsn(&self, data: &MapSv, lock: Lock, collect: &Collect<'_>) -> Resp {
        let _ = lock.lock().await;

        let task_sn = data.ck_str("k")?;
        let psn = data.ck_array("v")?;
        let k_task = collect.key_sub_sn("task", "psn", &task_sn)?;

        let rmpsn = match data.k_bool("rmpsn") {
            Some(v) => v,
            None => true,
        };

        let mut put = Vec::new();
        let mut del = Vec::new();
        let mut psn_set = HashSet::new();

        for d in psn.iter() {
            let d = d.ckstr()?;
            let k_psn = collect.key_sn("psn", d)?;

            psn_set.insert(d.to_string());

            if rmpsn {
                del.push(k_psn);
            }
        }

        collect.del_mass_set(&k_task, &psn_set, false, &mut put, &mut del);

        let _ = collect.del_station_psn(&psn_set, &mut put, &mut del).await;

        collect.commit(Some(&put), Some(&del)).await?;

        ok!()
    }

    fn ck_val(&self, val: &MapSv) -> Result<(String, String, String, String), Value> {
        let task_sn = val.ck_str_empty_trim("sn")?;
        let tech_sn = val.ck_str_empty_trim("tech")?;
        let state = val.ck_str_empty_trim("state")?;
        let pstart = val.ck_str_empty_trim("pstart")?;
        let pend = val.ck_str_empty_trim("pend")?;
        let pnum = val.ck_i64("pnum")?;

        if pnum <= 0 {
            throw_err!(ECode::Para, "pnum must gt 0");
        }

        let tm_pstart = self.from_iso(pstart)?;
        let tm_pend = self.from_iso(pend)?;

        let ck = tm_pstart.format("%Y-%m-%d %H:%M:%S").to_string();
        if ck != pstart {
            throw_err!(ECode::Para, "pstart format error");
        }

        let ck = tm_pend.format("%Y-%m-%d %H:%M:%S").to_string();
        if ck != pend {
            throw_err!(ECode::Para, "pend format error");
        }

        if tm_pstart >= tm_pend {
            throw_err!(ECode::Para, "pend must gt pstart");
        }

        Ok((
            tech_sn.to_string(),
            task_sn.to_string(),
            pstart.to_string(),
            state.to_string(),
        ))
    }

    async fn do_run(&self, data: &MapSv, lock: Lock, collect: &Collect<'_>) -> Resp {
        let _ = lock.lock().await;

        let mut ret = Vec::new();
        let run = collect.content_run("task").await?;

        let withpsn = match data.k_bool("withpsn") {
            Some(v) => v,
            None => false,
        };

        let is_count = match data.k_bool("count") {
            Some(v) => v,
            None => false,
        };

        let mut ignore = vec![];
        let mut include = vec![];

        if let Some(body) = data.k_array("igs") {
            for v in body.iter() {
                let v = v.ckstr()?;
                ignore.push(v);
            }
        }

        if let Some(body) = data.k_array("ins") {
            for v in body.iter() {
                let v = v.ckstr()?;
                include.push(v);
            }
        }

        for d in run.iter() {
            let task_sn = &d[20..];

            let mut ig = false;

            for s in ignore.iter() {
                if task_sn.starts_with(s) {
                    ig = true;
                    break;
                }
            }

            if ig {
                continue;
            }

            let mut ins = false;

            for s in include.iter() {
                if !task_sn.starts_with(s) {
                    ins = true;
                    break;
                }
            }

            if ins {
                continue;
            }

            if let Ok(mut item) = collect.item_sn("task", task_sn).await {
                if withpsn || is_count {
                    if let Some(item) = item.as_object_mut() {
                        let k_sn = collect.key_sub_sn("task", "psn", task_sn)?;

                        if let Ok(p) = collect.list_array(&k_sn, "psn", None).await {
                            if is_count {
                                if let Some(t) = p.as_array() {
                                    item.insert("psn_num".to_string(), json!(t.len()));
                                }
                            }

                            if withpsn {
                                item.insert("psn".to_string(), p);
                            }
                        }
                    }
                }

                ret.push(item);
            }
        }

        if ret.is_empty() {
            throw_err!(ECode::EmptyData, "no data");
        }

        ok!(ret)
    }

    async fn do_add(&self, data: &MapSv, lock: Lock, collect: &Collect<'_>, tx: &CSend) -> Resp {
        let _ = lock.lock().await;

        let val = data.ck_obj("v")?;

        let (tech_sn, task_sn, pstart, state) = self.ck_val(val)?;

        let k_task = collect.key_sn("task", &task_sn)?;
        let k_tech = collect.key_sn("tech", &tech_sn)?;

        let tech_body = match collect.item(&k_tech).await {
            Ok(v) => v,
            Err(_) => {
                throw_err!(ECode::Tech, "no tech");
            }
        };

        if let Ok(_) = collect.item(&k_task).await {
            throw_err!(ECode::Task, "repeat");
        }

        let tp = match tech_body.k_str("tp") {
            Some(v) => v,
            None => "",
        };

        let mut val = val.clone();
        val.insert("tp".to_string(), json!(tp));

        let mut put = vec![(k_task, json!(val))];

        let help = Level::new("task", collect);
        help.add_sn_to_day(&pstart, &task_sn, &mut put)?;

        if state == "open" {
            self.add_run(&task_sn, &pstart, collect, tx).await?;
        } else if state == "pause" {
            let k_pause = collect.key_pause("task")?;
            collect.merge_mass(&k_pause, &task_sn, &mut put);
        }

        collect.commit(Some(&put), None).await?;

        ok!()
    }

    async fn md_run(
        &self,
        sn: &str,
        pstart: &str,
        state: &str,
        collect: &Collect<'_>,
        tx: &CSend,
    ) -> RNull {
        let k_run = collect.key_run("task")?;

        if let Ok(mut body) = collect.content(&k_run).await {
            if let Some(p) = body.iter().position(|k| k.ends_with(sn)) {
                body.remove(p);
            }

            if state == "open" {
                body.push(format!("{pstart}#{sn}"));
            }

            body.sort_unstable();
            body.dedup();

            let mut put = Vec::new();
            let mut del = Vec::new();

            if body.is_empty() {
                del.push(k_run);
            } else {
                put.push((k_run, json!(body)));
            }

            collect.commit_que(Some(&put), Some(&del), tx).await?;
        }

        Ok(())
    }

    async fn add_run(
        &self,
        sn: &str,
        pstart: &str,
        collect: &Collect<'_>,
        tx: &CSend,
    ) -> Result<(), Value> {
        let k_run = collect.key_run("task")?;

        let mut tmp = vec![format!("{pstart}#{sn}")];

        if let Ok(mut body) = collect.content(&k_run).await {
            tmp.append(&mut body);
        }

        tmp.sort_unstable();
        tmp.dedup();

        let tmp = vec![(k_run, json!(tmp))];
        collect.commit_que(Some(&tmp), None, tx).await?;

        Ok(())
    }
}
